/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Copyright 2002-2003 by Mark Wolgemuth, All rights reserved.             */
/* Copyright 2002-2003 by Employease, Inc.  All rights reserved.           */
/*                                                                         */
/* Redistribution and use in source and binary forms, with or without      */
/* modification, are permitted provided that the following conditions      */
/* are met:                                                                */
/*                                                                         */
/*    1. Redistributions of source code must retain the above copyright    */
/* notice, this list of conditions and the following disclaimer.           */
/*    2. Redistributions in binary form must reproduce the above copyright */
/* notice, this list of conditions and the following disclaimer in the     */
/* documentation and/or other materials provided with the distribution.    */
/*                                                                         */
/* THIS SOFTWARE IS PROVIDED BY THE MOD_ATHENA PROJECT ``AS IS'' AND ANY   */
/* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE       */
/* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR      */
/* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FREEBSD PROJECT OR        */
/* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,   */
/* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,     */
/* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR      */
/* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY     */
/* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT            */
/* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "athd.h"

/* begin_private */
#define CLEANUPNOW (65536) /* size lim on mem used for log strings */
static int _cleanup = 0;
static int _log_lvl = ATHD_DEFAULT_LOGLVL;
static pthread_mutex_t *_athd_thread_mutex = NULL;
static apr_global_mutex_t *_athd_global_mutex = NULL;
static apr_file_t *_athd_log_fh = NULL;
static apr_pool_t *_pool_perm = NULL;
static apr_pool_t *_pool_tmp = NULL;
static int _use_syslog = 0;

static int          _athd_log_sig_start();
static int          _athd_log_sig_shutdown();
static void         _athd_log_cleanup();
static const char * _athd_log_strlvl(int lvl);
static void         _athd_log_tlock();
static void         _athd_log_tunlock();
static apr_status_t _athd_log_glock();
static apr_status_t _athd_log_gunlock();
/* end_private */

apr_status_t
athd_log_init(apr_pool_t *pglobal)
{
    apr_status_t rv = APR_EINIT;
    athd_conf *conf = athd_conf_current_get();
    ASSERT(conf);
    ASSERT(!_athd_global_mutex);
    ASSERT(!_athd_thread_mutex);
    apr_pool_create(&_pool_perm, pglobal);
    apr_pool_create(&_pool_tmp, _pool_perm);

    _athd_thread_mutex = apr_pcalloc(_pool_perm, sizeof(pthread_mutex_t));
    apr_pool_cleanup_register(_pool_perm, _athd_thread_mutex,
			      pthread_mutex_cleanup, apr_pool_cleanup_null);

    pthread_mutex_init(_athd_thread_mutex, NULL);
    rv = apr_global_mutex_create(&_athd_global_mutex, conf->log_lock_path, 
				 ath_lockmech, _pool_perm);   
    if (!rv)
      { 
	  if (conf->logfile)
	      rv =  apr_file_open(&_athd_log_fh, conf->logfile, 
				  APR_WRITE|APR_APPEND|APR_CREATE, 
				  APR_OS_DEFAULT, _pool_perm);
	  else if (conf->syslog_fac > 0)
	    {
		openlog(PACKAGE, LOG_PID, conf->syslog_fac);
		_use_syslog = 1;
	    }
      }
	  
    /* for now exit here */
    if (rv != APR_SUCCESS) 
	ATHD_PRINTERR_CRIT("Died: %s log: %s lock: %s", 
			   athd_err_str(_pool_tmp, rv), conf->logfile, 
			   conf->log_lock_path);
    if (conf->debug > -1)
	athd_log_set_lvl(conf->debug);
    athd_sig_handler_register(_athd_log_sig_start, "Logger", ATHD_START);
    athd_sig_handler_register(_athd_log_sig_shutdown, "Logger", 
			      ATHD_SHUTDOWN);
    return rv;
}

void
athd_log_set_lvl(int lvl)
{
    _log_lvl = lvl;
}

const char *
athd_log_get_strlvl()
{
    return _athd_log_strlvl(_log_lvl);
}
    
apr_status_t
athd_log_write_str(const char *str)
{
    apr_status_t rv;
    apr_size_t length = strlen(str);
    apr_size_t written;
    ASSERT(str);
    rv = _athd_log_glock();
    if (!rv)
	rv = apr_file_write_full(_athd_log_fh, str, length, &written);
    _athd_log_gunlock();
    return rv;
}

apr_status_t
athd_log_err(int lvl, apr_status_t status, const char *fmt, ...)
{
    apr_status_t rv = APR_SUCCESS;
    va_list args;
    va_start(args, fmt);
    if (_pool_tmp)
      {
	  char *errstr = athd_err_str(_pool_tmp, status);
	  char *newfmt = apr_psprintf(_pool_tmp, "%s%s [code %d]",
				      fmt, errstr, status);
	  /* apr_pstrcat(_pool_tmp, fmt, errstr, NULL); */
	  rv = athd_logv(lvl, newfmt, args);
      }
    else
      {
	  fprintf(stderr, "Log: Error: no memory pool\nFailed message: ");
	  vfprintf(stderr, fmt, args);
	  fprintf(stderr, " Status: %d", status);
      }
    va_end(args);
    return rv;
}

apr_status_t
athd_log(int lvl, const char *fmt, ...)
{
    apr_status_t rv;
    va_list args;
    va_start(args, fmt);
    rv = athd_logv(lvl, fmt, args);
    va_end(args);
    return rv;
}

apr_status_t
athd_logv(int lvl, const char *fmt, va_list args)
{
    apr_status_t rv = APR_SUCCESS;
    if (lvl <= _log_lvl) 
      {
	  if ((_athd_global_mutex) && ((_athd_log_fh) || (_use_syslog)))
	    {
		apr_size_t length = 0;
		char *print, *realfmt;
		if (_cleanup >= CLEANUPNOW)
		    _athd_log_cleanup();
		
		_athd_log_glock();
		/* keep track of how much mem we're using */
		if (_use_syslog)
		  {
		      realfmt = apr_pstrcat(_pool_tmp, (_athd_log_strlvl(lvl)),
					    " ", fmt, "\n", NULL);
		      print = apr_pvsprintf(_pool_tmp, realfmt, args);
		      length = strlen(print);
		      syslog(LOG_NOTICE, print);
		  }
		else
		  {
		      apr_size_t written;
		      char *date;
		      
		      date = athd_log_time(_pool_tmp);
		      realfmt = 
			apr_pstrcat(_pool_tmp, "[", date, "] ", 
				    (_athd_log_strlvl(lvl)), 
				    " ", fmt, "\n", NULL);
		      print = apr_pvsprintf(_pool_tmp, realfmt, args);
		      length = strlen(print);
		      
		      rv = apr_file_write_full(_athd_log_fh, 
					       print, length, &written);
		      if (APR_STATUS_IS_EOF(rv)) 
			  rv = APR_SUCCESS;
		  }
		_cleanup += length;
		_athd_log_gunlock();
	    }
	  else
	    {
		fprintf(stderr, "[Pre log init] %s ", _athd_log_strlvl(lvl));
		vfprintf(stderr, fmt, args);
		fprintf(stderr, "\n");
		fflush(stderr);
	    }
      }
    return rv;
}

char *
athd_log_time(apr_pool_t *pool)
{
    char *time;
    _athd_log_tlock();
    time = apr_pcalloc(pool, sizeof(char) * (APR_CTIME_LEN));
    apr_ctime(time, (apr_time_now()));
    _athd_log_tunlock();
    return time;
}

/* privates below (oooooh my) */
static int
_athd_log_sig_start(int sig)
{
    _athd_log_cleanup();
    return APR_SUCCESS;
}

static int
_athd_log_sig_shutdown(int sig)
{
    if (_use_syslog)
	closelog();
    return APR_SUCCESS;
}

static void
_athd_log_cleanup()
{
    _athd_log_tlock();
    apr_pool_clear(_pool_tmp);
    _cleanup = 0;
    _athd_log_tunlock();
    /* this must be outside above sync or it dead locks */
    athd_log(ATHD_INFO, "Log mem pool cleanup done.");
}

static const char *
_athd_log_strlvl(int lvl)
{
    char *str;
    if (lvl == ATHD_CORE)
	str = "[core]";
    else if (lvl == ATHD_CRIT)
	str = "[crit]";
    else if (lvl == ATHD_WARN)
	str = "[warn]";
    else if (lvl == ATHD_NOTE)
	str = "[note]";
    else if (lvl == ATHD_INFO)
	str = "[info]";
    else if (lvl == ATHD_DEBUG)
	str = "[debug]";
    else if (lvl > ATHD_DEBUG)
	str = "[devel]";
    else 
	str = "[????]";
    return str;
}

static void
_athd_log_tlock()
{
    ASSERT(_athd_thread_mutex);
    pthread_mutex_lock(_athd_thread_mutex);
}

static void 
_athd_log_tunlock()
{
    ASSERT(_athd_thread_mutex);
    pthread_mutex_unlock(_athd_thread_mutex);
}
	  
static apr_status_t
_athd_log_glock()
{
    ASSERT(_athd_global_mutex);
    return apr_global_mutex_lock(_athd_global_mutex);
}

static apr_status_t
_athd_log_gunlock()
{
    ASSERT(_athd_global_mutex);
    return apr_global_mutex_unlock(_athd_global_mutex);
}
